package com.czmh.etc.onlinepubsys.utils;

import com.czmh.etc.common.core.utils.MD5Util;
import com.czmh.etc.onlinepubsys.entity.WeiXinPayRefund;
import com.czmh.etc.onlinepubsys.entity.WeiXinPrePay;
import com.czmh.etc.onlinepubsys.entity.WxPrepayOrderInfo;
import com.czmh.etc.onlinepubsys.enums.WeiXinTradeTypeEnum;
import com.czmh.etc.onlinepubsys.utils.wxpay.StringUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ResourceUtils;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyStore;
import java.util.*;

/**
 * @功能说明: 微信支付工具类
 * @创建者: ziyang
 * @创建时间: 2017年11月26日 上午11:31:35
 * @版本: V1.0
 */
@SuppressWarnings("all")
public class WeiXinPayUtils {

    private static final Logger LOG = LoggerFactory.getLogger(WeiXinPayUtils.class);

    /**
     * 发送xml数据,获取返回结果
     *
     * @param requestUrl
     * @param requestMethod
     * @param xmlStr
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> httpXmlRequest(String requestUrl, String requestMethod, String xmlStr) {
        // 将解析结果存储在HashMap中
        Map<String, Object> map = new HashMap<String, Object>();
        OutputStream outputStream = null;
        InputStreamReader inputStreamReader = null;
        InputStream inputStream = null;
        HttpsURLConnection urlCon = null;
        try {
            urlCon = (HttpsURLConnection) (new URL(requestUrl)).openConnection();
            urlCon.setDoInput(true);
            urlCon.setDoOutput(true);
            // 设置请求方式（GET/POST）
            urlCon.setRequestMethod(requestMethod);

            if ("GET".equalsIgnoreCase(requestMethod)) {
                urlCon.connect();
            }

            urlCon.setRequestProperty("Content-Length", String.valueOf(xmlStr.getBytes().length));
            urlCon.setUseCaches(false);
            // 设置为gbk可以解决服务器接收时读取的数据中文乱码问题
            if (null != xmlStr) {
                outputStream = urlCon.getOutputStream();
                outputStream.write(xmlStr.getBytes("UTF-8"));
                outputStream.flush();
                // outputStream.close();
            }
            inputStream = urlCon.getInputStream();
            inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
            // 读取输入流
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStreamReader);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            List<Element> elementList = root.elements();
            // 遍历所有子节点
            for (Element e : elementList) {
                map.put(e.getName(), e.getText());
            }
            // inputStreamReader.close();
            // inputStream.close();
            // inputStream = null;
            // urlCon.disconnect();
        } catch (MalformedURLException e) {
            LOG.error(e.getMessage());
        } catch (IOException e) {
            LOG.error(e.getMessage());
        } catch (Exception e) {
            LOG.error(e.getMessage());
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
            if (inputStreamReader != null) {
                try {
                    inputStreamReader.close();
                } catch (Exception e3) {
                    e3.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                    inputStream = null;
                } catch (Exception e4) {
                    e4.printStackTrace();
                }
            }
            if (urlCon != null) {
                try {
                    urlCon.disconnect();
                } catch (Exception e5) {
                    e5.printStackTrace();
                }
            }
        }
        return map;
    }

    /**
     * 生成预支付XML
     *
     * @param weiXinPrePay
     * @param partnerKey
     * @return
     */
    public static String getPrePayXml(WeiXinPrePay weiXinPrePay, String partnerKey) {
        getPrePaySign(weiXinPrePay, partnerKey);// 生成预支付请求签名
        StringBuilder sb = new StringBuilder();
        sb.append("<xml><appid>").append(weiXinPrePay.getAppid()).append("</appid>");
        if (StringUtils.isNotEmpty(weiXinPrePay.getAttach())) {
            sb.append("<attach>").append(weiXinPrePay.getAttach()).append("</attach>");
        }
        sb.append("<body>").append(weiXinPrePay.getBody()).append("</body>");
        sb.append("<mch_id>").append(weiXinPrePay.getMchId()).append("</mch_id>");
        sb.append("<nonce_str>").append(weiXinPrePay.getNonceStr()).append("</nonce_str>");
        sb.append("<notify_url>").append(weiXinPrePay.getNotifyUrl()).append("</notify_url>");
        sb.append("<out_trade_no>").append(weiXinPrePay.getOutTradeNo()).append("</out_trade_no>");
        sb.append("<spbill_create_ip>").append(weiXinPrePay.getSpbillCreateIp()).append("</spbill_create_ip>");
        sb.append("<total_fee>").append(weiXinPrePay.getTotalFee()).append("</total_fee>");
        sb.append("<trade_type>").append(weiXinPrePay.getTradeType().name()).append("</trade_type>");
        // if(StringUtils.isNotEmpty(weiXinPrePay.getDeviceInfo()))
        // sb.append("<device_info>").append(weiXinPrePay.getDeviceInfo()).append("</device_info>");
        if (WeiXinTradeTypeEnum.NATIVE.name().equals(weiXinPrePay.getTradeType().name())) {
            sb.append("<product_id>").append(weiXinPrePay.getProductId()).append("</product_id>");
        } else if (WeiXinTradeTypeEnum.JSAPI.name().equals(weiXinPrePay.getTradeType().name())) {
            sb.append("<openid>").append(weiXinPrePay.getOpenid()).append("</openid>");
        }

        if (StringUtils.isNotEmpty(weiXinPrePay.getTimeStart())) {
            sb.append("<time_start>").append(weiXinPrePay.getTimeStart()).append("</time_start>");
        }
        if (StringUtils.isNotEmpty(weiXinPrePay.getTimeExpire())) {
            sb.append("<time_expire>").append(weiXinPrePay.getTimeExpire()).append("</time_expire>");
        }

        sb.append("<sign>").append(weiXinPrePay.getSign()).append("</sign>");
        sb.append("</xml>");

        return sb.toString();
    }

    /**
     * 微信拼接签名参数
     *
     * @param appid
     *            公众账号ID
     * @param mch_id
     *            商户号
     * @param device_info
     *            设备号
     * @param trade_type
     *            交易类型
     * @param prePay
     *            预支付返回Map
     * @param partnerKey
     *            签名EY
     * @return
     */
    public static String geWeiXintPrePaySign(String appid, String mch_id, String device_info, String trade_type,
            Map<String, Object> prePay, String partnerKey) {
        Map<String, Object> preParams = new HashMap<String, Object>();
        if (!StringUtil.isEmpty(prePay.get("return_code"))) {
            preParams.put("return_code", prePay.get("return_code"));
        }
        if (!StringUtil.isEmpty(prePay.get("return_msg"))) {
            preParams.put("return_msg", prePay.get("return_msg"));
        }
        if (!StringUtil.isEmpty(prePay.get("appid"))) {
            preParams.put("appid", appid);
        }
        if (!StringUtil.isEmpty(prePay.get("mch_id"))) {
            preParams.put("mch_id", mch_id);
        }
        // if (!StringUtil.isEmpty(prePay.get("device_info"))) {
        // preParams.put("device_info", device_info);
        // }
        if (!StringUtil.isEmpty(prePay.get("nonce_str"))) {
            preParams.put("nonce_str", prePay.get("nonce_str"));
        }
        if (!StringUtil.isEmpty(prePay.get("result_code"))) {
            preParams.put("result_code", prePay.get("result_code"));
        }
        if ("FAIL".equals(prePay.get("result_code"))) {
            if (!StringUtil.isEmpty(prePay.get("err_code"))) {
                preParams.put("err_code", prePay.get("err_code"));
            }
            if (!StringUtil.isEmpty(prePay.get("err_code_des"))) {
                preParams.put("err_code_des", prePay.get("err_code_des"));
            }
        }
        if (!StringUtil.isEmpty(prePay.get("trade_type"))) {
            preParams.put("trade_type", trade_type);
        }
        if (!StringUtil.isEmpty(prePay.get("prepay_id"))) {
            preParams.put("prepay_id", prePay.get("prepay_id"));
        }
        if (!StringUtil.isEmpty(prePay.get("code_url"))) {
            preParams.put("code_url", prePay.get("code_url"));
        }
        String argPreSign = getStringByMap(preParams) + "&key=" + partnerKey;
        String preSign = MD5Util.encode(argPreSign).toUpperCase();
        return preSign;
    }

    public static boolean notifySign(Map<String, String> result, String sign, String partnerKey) {
        String argNotifySign = getStringByStringMap(result) + "&key=" + partnerKey;
        String notifySign = MD5Util.encode(argNotifySign).toUpperCase();
        if (notifySign.equals(sign)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * 获取预支付请求签名
     *
     * @param weiXinPrePay
     * @param partnerKey
     * @return
     */
    private static String getPrePaySign(WeiXinPrePay weiXinPrePay, String partnerKey) {
        Map<String, Object> prePayMap = new HashMap<String, Object>();
        prePayMap.put("appid", weiXinPrePay.getAppid());// 公众账号ID
        if (StringUtils.isNotEmpty(weiXinPrePay.getAttach())) {
            prePayMap.put("attach", weiXinPrePay.getAttach());// 附加数据
        }
        prePayMap.put("body", weiXinPrePay.getBody()); // 商品描述
        prePayMap.put("mch_id", weiXinPrePay.getMchId()); // 商户号
        prePayMap.put("nonce_str", weiXinPrePay.getNonceStr()); // 随机字符串
        prePayMap.put("notify_url", weiXinPrePay.getNotifyUrl()); // 接收财付通通知的URL
        prePayMap.put("out_trade_no", weiXinPrePay.getOutTradeNo()); // 商户订单号
        prePayMap.put("spbill_create_ip", weiXinPrePay.getSpbillCreateIp()); // 终端IP
        prePayMap.put("total_fee", weiXinPrePay.getTotalFee()); // 总金额
        prePayMap.put("trade_type", weiXinPrePay.getTradeType().name()); // 交易类型

        // if(StringUtils.isNotEmpty(weiXinPrePay.getDeviceInfo()))
        // prePayMap.put("device_info", weiXinPrePay.getDeviceInfo());
        if (StringUtils.isNotEmpty(weiXinPrePay.getTimeStart())) {
            prePayMap.put("time_start", weiXinPrePay.getTimeStart()); // 开始时间
        }
        if (StringUtils.isNotEmpty(weiXinPrePay.getTimeExpire())) {
            prePayMap.put("time_expire", weiXinPrePay.getTimeExpire()); // 截止时间
        }
        if (WeiXinTradeTypeEnum.NATIVE.name().equals(weiXinPrePay.getTradeType().name())) {
            prePayMap.put("product_id", weiXinPrePay.getProductId()); // 商品ID
        } else if (WeiXinTradeTypeEnum.JSAPI.name().equals(weiXinPrePay.getTradeType().name())) {
            prePayMap.put("openid", weiXinPrePay.getOpenid()); // openid
        }

        String argPreSign = getStringByMap(prePayMap) + "&key=" + partnerKey;
        String preSign = MD5Util.encode(argPreSign).toUpperCase();
        weiXinPrePay.setSign(preSign);
        return preSign;
    }

    /**
     * 根据Map获取排序拼接后的字符串
     *
     * @param map
     * @return
     */
    public static String getStringByMap(Map<String, Object> map) {
        SortedMap<String, Object> smap = new TreeMap<String, Object>(map);
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> m : smap.entrySet()) {
            sb.append(m.getKey()).append("=").append(m.getValue()).append("&");
        }
        sb.delete(sb.length() - 1, sb.length());
        return sb.toString();
    }

    public static String getStringByStringMap(Map<String, String> map) {
        SortedMap<String, Object> smap = new TreeMap<String, Object>(map);
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> m : smap.entrySet()) {
            sb.append(m.getKey()).append("=").append(m.getValue()).append("&");
        }
        sb.delete(sb.length() - 1, sb.length());
        return sb.toString();
    }

    /**
     * 解析微信发来的请求（XML）
     *
     * @param inputStream
     * @return
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static SortedMap<String, String> parseXml(InputStream inputStream) throws Exception {

        if (inputStream == null) {
            return null;
        }
        SortedMap<String, String> map = new TreeMap<>();// 将解析结果存储在HashMap中
        try {
            SAXReader reader = new SAXReader();// 读取输入流
            Document document = reader.read(inputStream);
            Element root = document.getRootElement();// 得到xml根元素
            List<Element> elementList = root.elements();// 得到根元素的所有子节点
            for (Element e : elementList) { // 遍历所有子节点
                map.put(e.getName(), e.getText());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            // 释放资源
            inputStream.close();
        }

        return map;
    }

    public static void getAppPaySign(WxPrepayOrderInfo info, String apiSecret) {
        // 应用ID appid String(32) 是 wx8888888888888888 微信开放平台审核通过的应用APPID
        // 商户号 partnerid String(32) 是 1900000109 微信支付分配的商户号
        // 预支付交易会话ID prepayid String(32) 是 WX1217752501201407033233368018
        // 微信返回的支付交易会话ID
        // 扩展字段 package String(128) 是 Sign=WXPay 暂填写固定值Sign=WXPay
        // 随机字符串 noncestr String(32) 是 5K8264ILTKCH16CQ2502SI8ZNMTM67VS
        // 随机字符串，不长于32位。推荐随机数生成算法
        // 时间戳 timestamp String(10) 是 1412000000 时间戳，请见接口规则-参数规定
        // 签名 sign String(32) 是 C380BEC2BFD727A4B6845133519F3AD6 签名，详见签名生成算法
        Map<String, Object> prePayMap = new HashMap<String, Object>();
        prePayMap.put("appid", info.getAppId());// 公众账号ID
        prePayMap.put("partnerid", info.getPartnerid()); // 商品描述
        prePayMap.put("prepayid", info.getPerpay_id()); // 商户号
        prePayMap.put("package", "Sign=WXPay"); // 随机字符串
        prePayMap.put("noncestr", info.getNonce_str()); // 接收财付通通知的URL
        prePayMap.put("timestamp", info.getTimestamp()); // 商户订单号

        String argPreSign = getStringByMap(prePayMap) + "&key=" + apiSecret;
        String preSign = MD5Util.encode(argPreSign).toUpperCase();
        info.setSign(preSign);
    }

    public static String getPayRefundXml(WeiXinPayRefund weiXinPayRefund, String wxPayAppSecret) {
        getPayRefundSign(weiXinPayRefund, wxPayAppSecret);
        StringBuilder sb = new StringBuilder();
        sb.append("<xml><appid>").append(weiXinPayRefund.getAppid()).append("</appid>");
        sb.append("<mch_id>").append(weiXinPayRefund.getMchId()).append("</mch_id>");
        sb.append("<nonce_str>").append(weiXinPayRefund.getNonceStr()).append("</nonce_str>");
        sb.append("<out_refund_no>").append(weiXinPayRefund.getOutRefundNo()).append("</out_refund_no>");
        sb.append("<out_trade_no>").append(weiXinPayRefund.getOutTradeNo()).append("</out_trade_no>");
        sb.append("<refund_fee>").append(weiXinPayRefund.getRefundFee()).append("</refund_fee>");
        sb.append("<total_fee>").append(weiXinPayRefund.getTotalFee()).append("</total_fee>");
        sb.append("<sign>").append(weiXinPayRefund.getSign()).append("</sign>");
        sb.append("</xml>");

        return sb.toString();
    }

    private static void getPayRefundSign(WeiXinPayRefund weiXinPayRefund, String partnerKey) {
        Map<String, Object> prePayMap = new HashMap<String, Object>();
        prePayMap.put("appid", weiXinPayRefund.getAppid());// 公众账号ID
        prePayMap.put("mch_id", weiXinPayRefund.getMchId()); // 商户号
        prePayMap.put("nonce_str", weiXinPayRefund.getNonceStr()); // 随机字符串
        prePayMap.put("out_trade_no", weiXinPayRefund.getOutTradeNo()); // 商户订单号
        prePayMap.put("out_refund_no", weiXinPayRefund.getOutRefundNo()); // 商户退款订单号
        prePayMap.put("total_fee", weiXinPayRefund.getTotalFee()); // 订单金额
        prePayMap.put("refund_fee", weiXinPayRefund.getRefundFee()); // 退款金额
        String argPreSign = getStringByMap(prePayMap) + "&key=" + partnerKey;
        String sign = MD5Util.encode(argPreSign).toUpperCase();
        weiXinPayRefund.setSign(sign);
    }

    /**
     * 申请退款
     *
     * @param url
     *            请求url
     * @param data
     *            xml参数
     * @param mchid
     *            证书密码..默认是MCHID(商户号)
     * @param mchid
     *            证书路径
     * @return
     * @throws Exception
     */
    @SuppressWarnings("deprecation")
    public static String doRefund(String url, String data, String mchid, String certpath) throws Exception {
        /**
         * 注意PKCS12证书 是从微信商户平台-》账户设置-》 API安全 中下载的
         */
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        ClassPathResource classPathResource = new ClassPathResource(certpath);
        InputStream inputStream =classPathResource.getInputStream();
        try {
            /**
             * 此处要改
             */
            keyStore.load(inputStream, mchid.toCharArray());// 这里写密码..默认是你的MCHID
        } finally {
            inputStream.close();
        }

        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchid.toCharArray())// 这里也是写密码的
                .build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1.2" }, null,
                SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        try {
            HttpPost httpost = new HttpPost(url); // 设置响应头信息
            httpost.addHeader("Connection", "keep-alive");
            httpost.addHeader("Accept", "*/*");
            httpost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            httpost.addHeader("Host", "api.mch.weixin.qq.com");
            httpost.addHeader("X-Requested-With", "XMLHttpRequest");
            httpost.addHeader("Cache-Control", "max-age=0");
            httpost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) ");
            httpost.setEntity(new StringEntity(data, "UTF-8"));
            CloseableHttpResponse response = httpclient.execute(httpost);
            try {
                HttpEntity entity = response.getEntity();
                String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");
                EntityUtils.consume(entity);
                return jsonStr;
            } finally {
                response.close();
            }
        } finally {
            httpclient.close();
        }
    }

    /**
     *
     * @param pagePrams
     * @param wxPayAppSecret
     * @return
     */
    public static String generatePaySignature(Map<String, Object> pagePrams, String wxPayAppSecret) {
        String argPreSign = getStringByMap(pagePrams) + "&key=" + wxPayAppSecret;
        String paySign = MD5Util.encode(argPreSign).toUpperCase();
        return paySign;
    }

    public static SortedMap<String, String> parseXml(String respStr) {
        if (respStr == null) {
            return null;
        }
        SortedMap<String, String> map = new TreeMap<>();// 将解析结果存储在HashMap中
        try {
            Document document = DocumentHelper.parseText(respStr);
            Element root = document.getRootElement();// 得到xml根元素
            List<Element> elementList = root.elements();// 得到根元素的所有子节点
            for (Element e : elementList) { // 遍历所有子节点
                map.put(e.getName(), e.getText());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return map;
    }
}
